package org.xx.armory.commons;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;

import static org.xx.armory.commons.Validators.notNull;

/**
 * 转化工具类
 *
 * <p>
 * 实现了一组用于在各种类型中进行转化和格式化的工具方法
 * </p>
 *
 * @author Dev
 */
public final class Converter {
    /**
     *
     */
    private static final String RFC822_FORMAT = "EEE, dd MMM yyyy HH:mm:ss Z";

    /**
     *
     */
    private static final String ISO8601_FORMAT = "yyyy-MM-dd'T'HH:mm:ssX";

    /**
     *
     */
    private static final String S_DATE_FORMAT = "yyyy/M/d";

    /**
     *
     */
    private static final String G_DATE_FORMAT = "yyyy-M-d";

    /**
     *
     */
    private static final String[] DATE_FORMATS = new String[]{RFC822_FORMAT, ISO8601_FORMAT, S_DATE_FORMAT, G_DATE_FORMAT};

    /**
     * 包含类型和对应的属性访问器的映射的缓存。
     */
    private static final Map<Class<?>, Map<String, Method>> CLASS_PROPERTY_GETTERS_CACHE = new WeakHashMap<>();

    /**
     * 禁止构造此对象
     */
    private Converter() {
        throw new UnsupportedOperationException();
    }

    /**
     * 将字符串解析为{@code java.util.Date}类型的值
     *
     * <p>
     * 如果待解析的字符串是{@code null}或者{@code length() == 0}则返回{@code null}
     * </p>
     * <p>
     * 首先尝试将该字符串解析为数值类型对象，如果能够解析则把该数值类型对象的值看做纪元以来的毫秒数， 并转化为{@code java.util.Date}对象。
     * </p>
     * <p>
     * 否则依次使用下面的格式尝试解析字符串
     * </p>
     * <table>
     * <caption>格式表</caption>
     * <tr>
     * <th>类型</th>
     * <th>格式</th>
     * </tr>
     * <tr>
     * <td>RFC822</td>
     * <td><code>EEE, dd MMM yyyy HH:mm:ss Z</code></td>
     * </tr>
     * <tr>
     * <td>ISO8601</td>
     * <td><code>yyyy-MM-dd'T'HH:mm:ssX</code></td>
     * </tr>
     * <tr>
     * <td>Simple</td>
     * <td><code>yyyy/M/d</code></td>
     * </tr>
     * <tr>
     * <td>Generic</td>
     * <td><code>yyyy-M-d</code></td>
     * </tr>
     * </table>
     *
     * @param s
     *         需解析的字符串
     * @return 如果解析成功则返回解析后的日期，如果任何一种格式都无法解析成功则返回<code>null</code>。
     * @see java.text.SimpleDateFormat
     * @see java.util.Date#Date(long)
     */
    public static Date parseDate(
            String s
    ) {
        if (s == null || s.isEmpty()) {
            return null;
        }

        final long ts = NumberUtils.toLong(s, Long.MIN_VALUE);
        if (ts != Long.MIN_VALUE) {
            return new Date(ts);
        }

        for (final String fmt : DATE_FORMATS) {
            final DateFormat sdf = new SimpleDateFormat(fmt);
            try {
                return sdf.parse(s);
            } catch (ParseException ex) {
                // 不关心解析异常。
            }
        }

        return null;
    }

    /**
     * 将{@code java.lang.Number}类型的对象转化为{@code java.util.Date}类型的对象
     *
     * <p>
     * 把该值看做纪元以来的毫秒数，并转化为{@code java.util.Date}对象。
     * </p>
     *
     * @param n
     *         待转化的数值
     * @return 对应的{@code java.util.Date}对象。
     * @see java.util.Date#Date(long)
     */
    public static Date toDate(
            Number n
    ) {
        if (n == null) {
            return null;
        }

        return new Date(n.longValue());
    }

    /**
     * 将字节数组格式化为十六进制格式的字符串。
     *
     * <p>
     * 字节数组中的每个元素被格式化为2位的十六进制字符，高位在前。
     * </p>
     * <p>
     * 示例：
     * </p>
     *
     * <pre>
     * <code>
     * formatHexString(null) == "";
     * formatHexString(new byte[0]) == "";
     * formatHexString(new byte[] { 0x25, 073, 0x99 }) == "257399"
     * </code>
     * </pre>
     *
     * @param b
     *         需格式化的字节数组
     * @return 采用十六进制格式表示的字符串，如果参数<code>b</code>的长度为0或者是<code>null</code>，那么返回空字符串。
     * @see java.lang.String#format
     */
    public static String formatHexString(
            byte[] b
    ) {
        if (b == null || b.length == 0) {
            return "";
        }

        final StringBuilder sb = new StringBuilder();
        for (final byte bb : b) {
            sb.append(String.format("%02x", bb));
        }

        return sb.toString();
    }

    /**
     * 将长整数通过截断的方式转化为整数。
     *
     * @param l
     *         待截断的长整数。
     * @return 截断的结果。
     */
    public static int truncateToInteger(
            long l
    ) {
        if (l > Integer.MAX_VALUE) {
            return Integer.MAX_VALUE;
        } else if (l < Integer.MIN_VALUE) {
            return Integer.MIN_VALUE;
        } else {
            return (int) (0x0FFFFFFFFL & l);
        }
    }

    /**
     * 将长整数通过截断的方式转化为字节。
     *
     * @param l
     *         待截断的长整数。
     * @return 截断的结果。
     */
    public static int truncateToByte(
            long l
    ) {
        if (l > Byte.MAX_VALUE) {
            return Byte.MAX_VALUE;
        } else if (l < Byte.MIN_VALUE) {
            return Byte.MIN_VALUE;
        } else {
            return (byte) (0x0FFL & l);
        }
    }

    /**
     * 将十六进制字符串解析为字节数组。
     *
     * <p>
     * 解析时自动跳过不在{@literal 0-9, a-f, A-F}范围内的所有字符。
     * </p>
     *
     * @param hex
     *         待解析的十六进制字符串
     * @return 解析得到的字节数组, 如果参数{@code hex}是{@code null}则返回长度为{@code 0}的数组。
     */
    public static byte[] toBytes(
            String hex
    ) {
        if (hex == null) {
            return new byte[0];
        }

        final StringBuilder sb = new StringBuilder(hex.length());
        for (int i = 0; i < hex.length(); ++i) {
            final char ch = Character.toLowerCase(hex.charAt(i));
            if (ch >= '0' && ch <= '9') {
                sb.append(ch);
            } else if (ch >= 'a' && ch <= 'f') {
                sb.append(ch);
            }
        }

        byte[] ret = new byte[sb.length() / 2];
        int j = 0;
        for (int i = 0; i < sb.length() - 1; i += 2) {
            final char ch1 = sb.charAt(i);
            final char ch2 = sb.charAt(i + 1);

            int b;
            if (ch1 >= '0' && ch1 <= '9') {
                b = ch1 - '0';
            } else {
                b = ch1 - 'a' + 0x0a;
            }
            b <<= 4;
            if (ch2 >= '0' && ch2 <= '9') {
                b += (ch2 - '0');
            } else {
                b += (ch2 - 'a' + 0x0a);
            }
            ret[j++] = (byte) b;
        }

        return ret;
    }

    /**
     * 将数组转化为相同内容的列表, 转化的列表内容不能修改。
     *
     * @param <T>
     *         列表的元素类型
     * @param items
     *         待转化的数组
     * @return 转化后的列表, 如果参数{@code items}是{@code null}则转化的列表不包含任何元素。
     */
    public static <T> java.util.List<T> toList(
            T[] items
    ) {
        if (items == null) {
            return java.util.Collections.unmodifiableList(new java.util.ArrayList<T>(0));
        } else {
            return java.util.Collections.unmodifiableList(java.util.Arrays.asList(items));
        }
    }

    /**
     * 将可迭代对象转化为相同内容的列表, 转化的列表内容不能修改。
     *
     * @param <T>
     *         列表的元素类型。
     * @param items
     *         待转化的可迭代对象。
     * @return 转化后的列表, 如果参数{@code items}是{@code null}则转化的列表不包含任何元素。
     */
    public static <T> java.util.List<T> toList(
            Iterable<? extends T> items
    ) {
        final java.util.List<T> lst = new java.util.ArrayList<>(0);

        if (items != null) {
            for (final T item : items) {
                lst.add(item);
            }
        }

        return lst;
    }

    /**
     * 将数值集合转化为字节数组。
     *
     * <p>
     * 数值集合中的每个元素都被转化为一个字节, 转化过程中可能出现截断和舍入, 如果某个元素是{@code null}则转化为{@code 0}。
     * </p>
     *
     * @param c
     *         待转化的数值集合。
     * @return 转化后的字节数组。
     * @see Number#byteValue()
     */
    public static byte[] toByteArray(
            Collection<? extends Number> c
    ) {
        if (c == null || c.size() == 0) {
            return new byte[0];
        }

        final byte[] ret = new byte[c.size()];
        int i = 0;
        for (final Number n : c) {
            ret[i++] = n == null ? 0 : n.byteValue();
        }

        return ret;
    }

    /**
     * 将数值集合转化为整数数组。
     *
     * <p>
     * 数值集合中的每个元素都被转化为一个整数, 转化过程中可能出现截断和舍入, 如果某个元素是{@code null}则转化为{@code 0}。
     * </p>
     *
     * @param c
     *         待转化的数值集合。
     * @return 转化后的整数数组。
     * @see Number#intValue()
     */
    public static int[] toIntegerArray(
            Collection<? extends Number> c
    ) {
        if (c == null || c.size() == 0) {
            return new int[0];
        }

        final int[] ret = new int[c.size()];
        int i = 0;
        for (final Number n : c) {
            ret[i++] = n == null ? 0 : n.intValue();
        }

        return ret;
    }

    /**
     * 将数值集合转化为长整数数组。
     *
     * <p>
     * 数值集合中的每个元素都被转化为一个长整数, 转化过程中可能出现截断和舍入, 如果某个元素是{@code null}则转化为{@code 0L}。
     * </p>
     *
     * @param c
     *         待转化的数值集合。
     * @return 转化后的长整数数组。
     * @see Number#longValue()
     */
    public static long[] toLongArray(
            Collection<? extends Number> c
    ) {
        if (c == null || c.size() == 0) {
            return new long[0];
        }

        final long[] ret = new long[c.size()];
        int i = 0;
        for (final Number n : c) {
            ret[i++] = n == null ? 0L : n.longValue();
        }

        return ret;
    }

    /**
     * 将数值集合转化为双精度数组。
     *
     * <p>
     * 数值集合中的每个元素都被转化为一个双精度数, 转化过程中可能出现舍入, 如果某个元素是{@code null}则转化为{@code 0.0D}。
     * </p>
     *
     * @param c
     *         待转化的数值集合。
     * @return 转化后的双精度数组。
     * @see Number#doubleValue()
     */
    public static double[] toDoubleArray(
            Collection<? extends Number> c
    ) {
        if (c == null || c.size() == 0) {
            return new double[0];
        }

        final double[] ret = new double[c.size()];
        int i = 0;
        for (final Number n : c) {
            ret[i++] = n == null ? 0D : n.doubleValue();
        }

        return ret;
    }

    /**
     * 将字符串数组转化为{@code int}数组。
     *
     * @param s
     *         需要转化的字符串数组。
     * @return 如果{@code s == null || s.length == 0}那么返回长度为0的{@code int}数组。否则按照传入的数组的元素次序逐个转化为整数。
     */
    public static int[] toIntegerArray(
            String[] s
    ) {
        if (s == null || s.length == 0) {
            return new int[0];
        } else {
            int[] ret = new int[s.length];
            for (int i = 0; i < s.length; ++i) {
                ret[i] = Integer.parseInt(s[i].trim());
            }
            return ret;
        }
    }

    /**
     * 将整数转化为对应的{@code ValueEnum}枚举。
     *
     * <p>
     * 返回的枚举值{@code r}必须满足条件：{@code r.value() == i}。 如果找不到满足条件的枚举值，那么返回{@code null }。
     * </p>
     *
     * @param <VT>
     *         枚举类型
     * @param enumClass
     *         实现了{@code ValueEnum}接口的枚举类型。
     * @param i
     *         需要转化的整数。
     * @return 转化后的{@code ValueEnum}枚举值。
     * @throws IllegalArgumentException
     *         如果参数{@code enumClass}是{@code null}。
     * @see #toValueEnum(Object[], long)
     */
    public static <VT extends Enum<?> & ValueEnum> VT toValueEnum(
            Class<VT> enumClass,
            long i
    ) {
        return enumClass.cast(toValueEnum(enumClass.getEnumConstants(), i));
    }

    /**
     * 将{@code Number}转化为对应的{@code ValueEnum}枚举。
     *
     * <p>
     * 返回的枚举值{@code r}必须满足条件：{@code r.value() == n.intValue()}。 如果{@code n == null}
     * 或者找不到满足条件的枚举值，那么返回{@code null }。
     * </p>
     *
     * @param <VT>
     *         枚举类型
     * @param enumClass
     *         实现了{@code ValueEnum}接口的枚举类型。
     * @param n
     *         需要转化的{@code Number}。
     * @return 转化后的{@code ValueEnum}枚举值。
     * @throws NullPointerException
     *         如果参数{@code enumClass == null}。
     * @see #toValueEnum(Class, long)
     */
    public static <VT extends Enum<?> & ValueEnum> VT toValueEnum(
            Class<VT> enumClass,
            Number n
    ) {
        return n == null ? null : enumClass.cast(toValueEnum(enumClass.getEnumConstants(), n.longValue()));
    }

    /**
     * 将{@link String}转化为对应的{@link StringEnum}枚举。
     *
     * <p>
     * 返回的枚举值{@code r}必须满足条件：{@code r.stringValue() == s}。 如果{@code n == null}
     * 或者找不到满足条件的枚举值，那么返回{@code null }。
     * </p>
     *
     * @param <ST>
     *         枚举类型
     * @param enumClass
     *         实现了{@link StringEnum}接口的枚举类型。
     * @param s
     *         需要转化的{@link String}。
     * @return 转化后的{@link StringEnum}枚举值。
     * @throws NullPointerException
     *         如果参数{@code enumClass == null}。
     */
    public static <ST extends Enum<?> & StringEnum> ST toStringEnum(
            Class<ST> enumClass,
            String s
    ) {
        return s == null ? null : enumClass.cast(toStringEnum(enumClass.getEnumConstants(), s));
    }

    /**
     * 将整数转化为对应的{@code ValueEnum}值。
     *
     * <p>
     * 遍历{@code constants}的每个元素，返回满足条件：{@code value() == i}的值。如果某个元素没有实现{@code ValueEnum}
     * 接口，那么跳过该元素。如果找不到满足条件的值，那么返回{@code null }。
     * </p>
     *
     * @param constants
     *         待遍历的数组。
     * @param i
     *         需要转化的整数。
     * @return 转化后的{@code ValueEnum}值。
     */
    public static Object toValueEnum(
            Object[] constants,
            long i
    ) {
        if (constants == null) {
            return null;
        }

        for (final Object v : constants) {
            if (!(v instanceof ValueEnum)) {
                continue;
            }

            if (((ValueEnum) v).value() == i) {
                return v;
            }
        }

        return null;
    }

    /**
     * 将字符串转化为对应的{@code StringEnum}值。
     *
     * <p>
     * 遍历{@code constants}的每个元素，返回满足条件：{@code value() == i}的值。如果某个元素没有实现{@code StringEnum}
     * 接口，那么跳过该元素。如果找不到满足条件的值，那么返回{@code null }。
     * </p>
     *
     * @param constants
     *         待遍历的数组。
     * @param s
     *         需要转化的字符串。
     * @return 转化后的{@code StringEnum}值。
     */
    public static Object toStringEnum(
            Object[] constants,
            String s
    ) {
        if (constants == null || s == null) {
            return null;
        }

        for (final Object v : constants) {
            if (!(v instanceof StringEnum)) {
                continue;
            }

            if (s.equals(((StringEnum) v).stringValue())) {
                return v;
            }
        }

        return null;
    }

    /**
     * 将若干枚举值对应的整数值通过逗号连接为一个字符串。
     *
     * @param <T>
     *         枚举类型
     * @param enums
     *         需要连接的枚举值
     * @return 连接后的字符串。
     * @throws NullPointerException
     *         如果参数{@code enums == null }
     * @throws IllegalArgumentException
     *         如果参数{@code enums.length == 0 }
     */
    public static <T extends Enum<T> & ValueEnum> String joinEnums(
            T[] enums
    ) {
        if (enums == null) {
            throw new NullPointerException();
        }
        if (enums.length == 0) {
            throw new IllegalArgumentException("enums");
        }

        final StringBuilder sb = new StringBuilder();

        boolean first = true;
        for (final T t : enums) {
            if (!first) {
                sb.append(",");
            }
            sb.append(t.value());
            first = false;
        }

        return sb.toString();
    }

    /**
     * 将若干枚举值对应的整数值通过逗号连接为一个字符串。
     *
     * @param <T>
     *         枚举类型
     * @param first
     *         第一个枚举值
     * @param others
     *         其它需要连接的枚举值
     * @return 连接后的字符串。
     */
    @SafeVarargs
    public static <T extends Enum<T> & ValueEnum> String joinEnums(
            T first,
            T... others
    ) {
        return joinEnums(",", first, others);
    }

    /**
     * 将若干枚举值对应的整数值通过指定的连接字符连接为一个字符串。
     *
     * @param <T>
     *         枚举类型
     * @param sep
     *         连接字符串
     * @param first
     *         第一个枚举值
     * @param others
     *         其它需要连接的枚举值
     * @return 连接后的字符串。
     */
    @SafeVarargs
    public static <T extends Enum<T> & ValueEnum> String joinEnums(
            String sep,
            T first,
            T... others
    ) {
        final StringBuilder sb = new StringBuilder();
        sb.append(first.value());

        if (others != null) {
            for (final T t : others) {
                sb.append(sep);
                sb.append(t.value());
            }
        }

        return sb.toString();
    }

    /**
     * 将对象转化为字符串。
     *
     * @param value
     *         待转化的对象。
     * @return 如果参数{@code value}是{@code null}则返回空字符串，否则返回{@code value.toString()}。
     */
    public static String toString(
            Object value
    ) {
        return value != null ? value.toString() : "";
    }

    /**
     * 将指定的字符串用双引号引起来。
     *
     * @param s
     *         需要引起来的字符串, 如果参数{@code s}是{@code null}则看作空字符串({@code ""})。
     * @return 用双引号引起来的结果字符串。
     * @see #quote(String, char)
     */
    public static String quote(
            String s
    ) {
        return quote(s, '"');
    }

    /**
     * 将指定的字符串用指定的字符引起来。
     *
     * @param s
     *         需要引起来的字符串。
     * @param q
     *         用来引用的字符。
     * @return 用指定的字符{@code q}引起来的结果字符串。
     */
    public static String quote(
            String s,
            char q
    ) {
        final StringBuilder sb = new StringBuilder(s == null ? 2 : s.length() + 2);
        sb.append(q);
        if (s != null && s.length() > 0) {
            sb.append(s);
        }
        sb.append(q);
        return sb.toString();
    }

    /**
     * 将{@code Object}类型的值转化为{@code long}类型的值，如果转化失败则返回缺省值。
     *
     * <p>
     * 转化规则：
     * </p>
     * <ul>
     * <li>{@code null}，看作转化失败，返回缺省值</li>
     * <li>{@code java.lang.Number}类型，直接调用{@code longValue()}</li>
     * <li>{@code java.util.Date}类型，直接调用{@code getTime()}</li>
     * <li>其它类型 ，先调用{@code toString()}转化为字符串，然后尝试通过{@code Long.valueOf(String)}方法进行转化
     * </ul>
     *
     * @param obj
     *         待转化的值，该参数可以是{@code null}
     * @param defaultValue
     *         缺省值
     * @return 转化的结果
     * @see java.lang.Number#longValue()
     * @see java.util.Date#getTime()
     * @see java.lang.Long#valueOf(String)
     */
    public static long toLong(
            Object obj,
            long defaultValue
    ) {
        if (obj == null) {
            return defaultValue;
        } else if (obj instanceof Number) {
            return ((Number) obj).longValue();
        } else if (obj instanceof java.util.Date) {
            return ((java.util.Date) obj).getTime();
        } else {
            try {
                return Long.valueOf(obj.toString().trim());
            } catch (NumberFormatException e) {
                return defaultValue;
            }
        }
    }

    /**
     * 将{@code Object}类型的值转化为{@code int}类型的值，如果转化失败则返回缺省值。
     *
     * <p>
     * 转化规则：
     * </p>
     * <ul>
     * <li>{@code null}，看作转化失败，返回缺省值</li>
     * <li>{@code java.lang.Number}类型，直接调用{@code intValue()}</li>
     * <li>其它类型 ，先调用{@code toString()}转化为字符串，然后尝试通过{@code Integer.valueOf(String)}方法进行转化
     * </ul>
     *
     * @param obj
     *         待转化的值，该参数可以是{@code null}
     * @param defaultValue
     *         缺省值
     * @return 转化的结果
     * @see java.lang.Number#intValue()
     * @see java.lang.Integer#valueOf(String)
     */
    public static int toInteger(
            Object obj,
            int defaultValue
    ) {
        if (obj == null) {
            return defaultValue;
        } else if (obj instanceof Number) {
            return ((Number) obj).intValue();
        } else {
            try {
                return Integer.valueOf(obj.toString().trim());
            } catch (NumberFormatException e) {
                return defaultValue;
            }
        }
    }

    /**
     * 将标识符改为camel格式。
     *
     * <p>
     * 将camel格式标识符的首字母改为大写就是Pascal格式。
     * </p>
     *
     * <pre>
     * 示例
     *
     * camel("abc") == "abc";
     * camel("a_bc") == "aBc";
     * camel("d-one_do") == "dOneDo";
     *
     * </pre>
     *
     * @param name
     *         需要修改的标识符
     * @return camel格式的结果, 如果参数{@code name}是{@code null}或者只包含空字符串, 那么返回空字符串。
     * @see StringUtils#isBlank(CharSequence)
     */
    public static String camel(
            String name
    ) {
        if (StringUtils.isBlank(name)) {
            return "";
        }

        final StringBuilder buf = new StringBuilder();

        boolean u = false;
        for (int i = 0; i < name.length(); ++i) {
            char ch = name.charAt(i);
            if (ch == '-' || ch == '_') {
                u = true;
                continue;
            }

            if (u) {
                ch = Character.toUpperCase(ch);
            }

            buf.append(ch);
            u = false;
        }

        return buf.toString();
    }

    /**
     * 获取指定类型的所有属性访问器。
     *
     * @param clazz
     *         指定的类型。
     * @return 指定类型的所有属性访问器。
     * @throws IllegalArgumentException
     *         如果参数{@code clazz}是{@code null}。
     */
    private static Map<String, Method> getPropertyGetters(
            Class<?> clazz
    ) {
        notNull(clazz, "clazz");

        final Map<String, Method> ret = new HashMap<>();

        for (final Method method : clazz.getMethods()) {
            // 跳过静态方法和参数不为0的方法。
            if ((method.getModifiers() & Modifier.STATIC) != 0 || method.getParameterCount() > 0) {
                continue;
            }

            final String methodName = method.getName();
            final String propertyNameU;
            switch (methodName) {
                case "getClass":
                case "get":
                case "is":
                    // 跳过特殊方法。
                    continue;
                default:
                    if (methodName.startsWith("get")) {
                        propertyNameU = methodName.substring(3);
                    } else if (methodName.startsWith("is")) {
                        if (method.getReturnType() == Boolean.class || method.getReturnType() == boolean.class) {
                            propertyNameU = methodName.substring(2);
                        } else {
                            continue;
                        }
                    } else {
                        continue;
                    }
            }

            // 属性名的首字母要求小写。
            final String propertyName = Character.toLowerCase(propertyNameU.charAt(0)) + propertyNameU.substring(1);

            method.setAccessible(true);
            ret.put(propertyName, method);
        }

        return ret;
    }

    /**
     * 将指定的对象转化为属性名值映射。
     *
     * @param obj
     *         待转化的对象。
     * @return 包含该对象的所有公共属性名值的映射，如果参数{@code obj}是{@code null}则返回空映射。
     */
    public static Map<String, Object> toMap(
            Object obj
    ) {
        final Map<String, Object> ret = new HashMap<>();

        if (obj != null) {
            final Class<?> clazz = obj.getClass();
            Map<String, Method> getters = CLASS_PROPERTY_GETTERS_CACHE.get(clazz);
            if (getters == null) {
                synchronized (CLASS_PROPERTY_GETTERS_CACHE) {
                    getters = CLASS_PROPERTY_GETTERS_CACHE.get(clazz);
                    if (getters == null) {
                        getters = getPropertyGetters(clazz);
                        CLASS_PROPERTY_GETTERS_CACHE.put(clazz, getters);
                    }
                }
            }

            for (final Map.Entry<String, Method> entry : getters.entrySet()) {
                final String key = entry.getKey();
                final Method getter = entry.getValue();
                final Object value;
                try {
                    value = getter.invoke(obj);
                } catch (ReflectiveOperationException ex) {
                    throw new IllegalStateException("cannot get " + obj.getClass().getCanonicalName() + "." + key, ex);
                }

                if (value != null) {
                    ret.put(key, value);
                }
            }
        }

        return ret;
    }
}
